package org.mbds.wolf.seql;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

import org.mbds.wolf.exception.WolfException;
import org.mbds.wolf.seql.exceptions.APDUClaException;
import org.mbds.wolf.seql.exceptions.APDUInsException;
import org.mbds.wolf.tools.BytesTool;

/**
 * 
 * @author amlesas
 *
 */
public class APDUCommand {
	public final static byte ZERO = 0x00;
	
	private byte[] apduCommand = null;
	private byte cla;
	private byte ins;
	private byte p1;
	private byte p2;
	private byte[] data;
	private byte[] lc;
	private byte[] le;
	private int lcVal;
	private int leVal;
	
	/**
	 * APDUCommand default constructor
	 */
	public APDUCommand() {
	}

	/**
	 * APDUCommand constructor
	 * @param apdu
	 * @throws WolfException 
	 * @throws UnsupportedEncodingException 
	 */
	public APDUCommand(byte[] apdu) throws WolfException {
		setApduCommand(apdu);
	}
	
	/**
	 * @return the cla
	 */
	public byte getCla() {
		return cla;
	}
	
	/**
	 * @param cla the cla to set
	 */
	public void setCla(byte cla) {
		this.cla = cla;
	}
	
	/**
	 * @return the ins
	 */
	public byte getIns() {
		return ins;
	}
	
	/**
	 * @param ins the ins to set
	 */
	public void setIns(byte ins) {
		this.ins = ins;
	}
	
	/**
	 * @return the p1
	 */
	public byte getP1() {
		return p1;
	}
	
	/**
	 * @param p1 the p1 to set
	 */
	public void setP1(byte p1) {
		this.p1 = p1;
	}
	
	/**
	 * @return the p2
	 */
	public byte getP2() {
		return p2;
	}
	
	/**
	 * @param p2 the p2 to set
	 */
	public void setP2(byte p2) {
		this.p2 = p2;
	}
	
	/**
	 * @return the data
	 */
	public byte[] getData() {
		return data;
	}
	
	/**
	 * @param data the data to set
	 */
	public void setData(byte[] data) {
		this.data = data;
	}

	/**
	 * @return the lc
	 */
	public byte[] getLc() {
		return lc;
	}

	/**
	 * @param lc the lc to set
	 */
	public void setLc(byte[] lc) {
		this.lc = lc;
	}

	/**
	 * @return the le
	 */
	public byte[] getLe() {
		return le;
	}

	/**
	 * @param le the le to set
	 */
	public void setLe(byte[] le) {
		this.le = le;
		setLeVal(BytesTool.byteArrayToIntValue(le));
	}
	/**
	 * @return the apduCommand
	 */
	public byte[] getApduCommand() {
		return apduCommand;
	}
	
	/**
	 * @param apduCommand the apduCommand to set
	 * @throws Exception 
	 */
	public void setApduCommand(byte[] apduCommand) throws WolfException {
		this.apduCommand = apduCommand;
		if (apduCommand.length>=ISO7816.OFFSET_CLA)
			setCla(apduCommand[ISO7816.OFFSET_CLA]);
		else 
			throw new APDUClaException();
		if (apduCommand.length>=ISO7816.OFFSET_INS)
			setIns(apduCommand[ISO7816.OFFSET_INS]);
		else
			throw new APDUInsException();
		if (apduCommand.length>=ISO7816.OFFSET_P1) {
			setP1(apduCommand[ISO7816.OFFSET_P1]);
			if (apduCommand.length>=ISO7816.OFFSET_P2)
				setP2(apduCommand[ISO7816.OFFSET_P2]);
			//No p2 (?)
			else return;
			//No p1, p2...
		} else return;
		// No data sent, no response expected ?
		if (apduCommand.length<=ISO7816.OFFSET_LC)
			return;
		////////////////////////////////////////
		int offsetData = ISO7816.OFFSET_LC;
		boolean isExtendedAPDU = false;
		boolean isLcDefined = true;
		int lcInt = 0;
		byte[] test;
		if (apduCommand[ISO7816.OFFSET_LC]==ZERO 
				&& apduCommand.length>ISO7816.OFFSET_LC+2) {
			//Check extended Lc or Lc not defined (no data sent, Le defined)
			test = new byte[3];
			System.arraycopy(apduCommand, ISO7816.OFFSET_LC,test , 0, 3);
			lcInt = BytesTool.byteArrayToIntValue(test);
			System.out.println("APDUCommand: Lc int value="+lcInt);
			if (apduCommand.length>ISO7816.OFFSET_LC+2+lcInt) {
				setLcVal(lcInt);
				isExtendedAPDU=true;
			} else {
				isLcDefined = false;
			}
		} else {
			if (apduCommand[ISO7816.OFFSET_LC]!=ZERO) {
				lcInt = BytesTool.byteArrayToIntValue(new byte[]{apduCommand[ISO7816.OFFSET_LC]});
				System.out.println("APDUCommand: Lc int value="+lcInt);
				if (apduCommand.length<ISO7816.OFFSET_LC+1+lcInt) {
					isLcDefined = false;
				} else {
					setLcVal(lcInt);
				}
			}
		}
		if (isLcDefined) {
			if (isExtendedAPDU) {
				offsetData = ISO7816.OFFSET_LC+3;
				setLc(Arrays.copyOfRange(apduCommand, ISO7816.OFFSET_LC, ISO7816.OFFSET_LC+2));

			} else {
				offsetData = ISO7816.OFFSET_LC+1;
				setLc(Arrays.copyOfRange(apduCommand, ISO7816.OFFSET_LC, ISO7816.OFFSET_LC));
			}	
			if (getLcVal()>0)
				setData(Arrays.copyOfRange(apduCommand, offsetData, offsetData + getLcVal()));
		}
		if (apduCommand.length>offsetData + getLcVal()) {
			if (apduCommand.length==offsetData + getLcVal()+1) {
				if (apduCommand[offsetData + getLcVal()]!=ZERO) {
					//Le = 1..255
					test = new byte[]{apduCommand[offsetData + getLcVal()]};
					setLe(test);
					setLeVal(BytesTool.byteArrayToIntValue(test));
				} else if (apduCommand[offsetData + getLcVal()]==ZERO) {
					//Le = 256
					test = new byte[]{(byte) 0xFF};
					setLe(test);
					setLeVal(BytesTool.byteArrayToIntValue(test));
				}
			} else if (apduCommand.length==offsetData + getLcVal()+2) { 
				if (apduCommand[offsetData + getLcVal()]!=ZERO 
						|| apduCommand[offsetData + getLcVal()+1]!=ZERO ) {
					//Le = 1..65535
					test = new byte[]{apduCommand[offsetData + getLcVal()], apduCommand[offsetData + getLcVal()+1]};
					setLe(test);
				} else {
					//Le = 65536
					test = new byte[]{(byte) 0xFF, (byte) 0xFF};
					setLe(test);
				}
				setLeVal(BytesTool.byteArrayToIntValue(test));
			} else { // Le on 3 bytes > 65536
				test = new byte[]{apduCommand[offsetData + getLcVal()], apduCommand[offsetData + getLcVal()+1], apduCommand[offsetData + getLcVal()+2]};
				setLe(test);
				setLeVal(BytesTool.byteArrayToIntValue(test));
			}	
		}
	}
	
	/**
	 * @param apduStr
	 * @throws Exception
	 */
	public void setApduCommand(String apduStr) throws WolfException {
		setApduCommand(BytesTool.hexStringToByteArray(apduStr));
	}
	
	@Override
	public String toString() {
		if (apduCommand == null) return null;
		try {
			return BytesTool.byteArrayToHexString(apduCommand);
		} catch (WolfException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * @return the lcVal
	 */
	public int getLcVal() {
		return lcVal;
	}

	/**
	 * @param lcVal the lcVal to set
	 */
	private void setLcVal(int lcVal) {
		this.lcVal = lcVal;
	}

	/**
	 * @return the leVal
	 */
	public int getLeVal() {
		return leVal;
	}

	/**
	 * @param leVal the leVal to set
	 */
	private void setLeVal(int leVal) {
		this.leVal = leVal;
	}
}
